using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Audio;

namespace Magnet.Engine
{
    /// <summary>
    /// Sound Manager class to play different sounds.
    /// 
    /// You may be wondering why I paused the music instead of stopping it.
    /// When a cue is stopped, it is disposed. When a cue is created (and gets a cue from
    /// the soundBank it is prepared. Thus, if a sound is stopped it will need to be 
    /// prepared again to play. It cannot be played again when it stops without being
    /// prepared first.
    /// 
    /// Everything is static because there is no need to have multiple instances of
    /// sound banks.
    /// 
    /// When adding sounds to XACT, make sure you use compression! The sound file sizes in this game
    /// went from >300mb to below 100mb with compression enabled. It isn't obvious how to add compression
    /// so some might think there is no compression, but that isn't true.
    /// 
    /// Cue life:
    /// Null -> Preparing -> Playing -> Stopping -> Null
    /// Prepared -> Playing (Play) -> Paused -> Playing (Resume)
    /// 
    /// As far as I know there is no way to restart music without preparing it again.
    /// </summary>
    public class Sound
    {

        #region Public

        public const String DEATH1 = "death1";
        public const String DEATH2 = "death2";
        public const String DEATH3 = "death3";

        public const String DEATH_FALL1 = "death_fall1";
        public const String DEATH_FALL2 = "death_fall2";
        public const String DEATH_FALL3 = "death_fall3";
        public const String DEATH_FALL4 = "death_fall4";

        public const String END_LEVEL1 = "end_level1";

        public const String LEVEL_END1 = "level_end1";
        public const String LEVEL_END2 = "level_end2";
        public const String LEVEL_END3 = "level_end3";
        public const String LEVEL_END4 = "level_end4";

        public const String LEVEL_START1 = "level_start1";
        public const String LEVEL_START2 = "level_start2";
        public const String LEVEL_START3 = "level_start3";

        public const String MENU_BACK = "menu_back";
        public const String MENU_SELECT = "menu_select";

        public const String MUSIC1 = "music_1";
        public const String MUSIC2 = "music_2";
        public const String MUSIC3 = "music_3";
        public const String MUSIC_INTRO = "music_intro";

        public const String PARTICLE_HIT1 = "particle_hit1";
        public const String PARTICLE_HIT2 = "particle_hit2";
        public const String PARTICLE_HIT3 = "particle_hit3";

        public const String PARTICLE_MOVE1 = "particle_move1";
        public const String PARTICLE_MOVE2 = "particle_move2";
        public const String PARTICLE_MOVE3 = "particle_move3";

        public const String POLARITY_CHANGE1 = "polarity_charge1";
        public const String POLARITY_CHANGE2 = "polarity_charge2";
        public const String POLARITY_CHANGE3 = "polarity_charge3";

        public const String SLOWMO_END1 = "slowmo_end1";
        public const String SLOWMO_END2 = "slowmo_end2";
        public const String SLOWMO_END3 = "slowmo_end3";

        public const String SLOWMO_START1 = "slowmo_start1";
        public const String SLOWMO_START2 = "slowmo_start2";
        public const String SLOWMO_START3 = "slowmo_start3";
        public const String SLOWMO_START4 = "slowmo_start4";

        public enum MusicList
        {
            None,
            Music1,
            Music2,
            Music3,
            MusicIntro
        }

        public static MusicList currentMusic = MusicList.None;

        /// <summary>
        /// Initializes this static object to be used to play music.
        /// </summary>
        public static void Initialize()
        {
            // Initialize audio objects.
            engine = new AudioEngine("Content\\Audio\\magnetSounds.xgs");
            soundBank = new SoundBank(engine, "Content\\Audio\\Sound Bank.xsb");
            waveBank = new WaveBank(engine, "Content\\Audio\\Wave Bank.xwb");

            ResetMusic();
        }

        /// <summary>
        /// Queues up a cue to be played after the next engine update.
        /// </summary>
        /// <param name="cueName">The string name of the cue to be played.</param>
        public static void PlayCue(String cueName)
        {
            Cue soundCue = soundBank.GetCue(cueName);

            if (soundCue != null)
                listOfCueRequests.Add(soundCue);
        }

        /// <summary>
        /// Flags the music to be changed.
        /// </summary>
        /// <param name="newMusic">New music that will be played after the next engine update</param>
        public static void ChangeMusic(MusicList newMusic)
        {
            changeMusic = true;
            currentMusic = newMusic;

            PauseAllMusic();
        }

        /// <summary>
        /// Updates the sound manger, which plays queued sounds, changes music (if called),
        /// and updates the sound engine.
        /// </summary>
        public static void Update()
        {
            // Update the audio engine.
            engine.Update();

            foreach (Cue cue in listOfCueRequests)
                cue.Play();

            listOfCueRequests.Clear();

            if (changeMusic)
                PlayNewMusic();

            if (currentMusic == MusicList.Music1)
            {
                if (!music1.IsPaused)
                {
                    if (music1.IsStopped)
                        music1 = soundBank.GetCue(MUSIC1);

                    if(music1.IsPrepared)
                        music1.Play();
                }
            }
            else if (currentMusic == MusicList.Music2)
            {
                if (!music2.IsPaused)
                {
                    if (music2.IsStopped)
                        music2 = soundBank.GetCue(MUSIC2);

                    if (music2.IsPrepared)
                        music2.Play();
                }
            }
            else if (currentMusic == MusicList.Music3)
            {
                if (!music3.IsPaused)
                {
                    if (music3.IsStopped)
                        music3 = soundBank.GetCue(MUSIC3);

                    if (music3.IsPrepared)
                        music3.Play();
                }
            }
            else if (currentMusic == MusicList.MusicIntro)
            {
                if (!musicIntro.IsPaused)
                {
                    if (musicIntro.IsStopped)
                        musicIntro = soundBank.GetCue(MUSIC_INTRO);

                    if (musicIntro.IsPrepared)
                        musicIntro.Play();
                }
            }
        }
        #endregion

        #region Private

        // Audio objects
        private static AudioEngine engine;
        private static SoundBank soundBank;
        private static WaveBank waveBank;

        private static List<Cue> listOfCueRequests = new List<Cue>();

        private static Cue music1;
        private static Cue music2;
        private static Cue music3;
        private static Cue musicIntro;

        private static bool changeMusic = false;

        /// <summary>
        /// Pauses all music only if the music is playing.
        /// </summary>
        private static void PauseAllMusic()
        {
            if (music1.IsPlaying)
                music1.Pause();
            if (music2.IsPlaying)
                music2.Pause();
            if (music3.IsPlaying)
                music3.Pause();
            if (musicIntro.IsPlaying)
                musicIntro.Pause();
        }

        /// <summary>
        /// Stops all music only if the music is playing
        /// (does not stop paused music)
        /// </summary>
        private static void StopAllMusic()
        {
            if (music1.IsPlaying)
                music1.Stop(AudioStopOptions.Immediate);
            if (music2.IsPlaying)
                music2.Stop(AudioStopOptions.Immediate);
            if (music3.IsPlaying)
                music3.Stop(AudioStopOptions.Immediate);
            if (musicIntro.IsPlaying)
                musicIntro.Stop(AudioStopOptions.Immediate);
        }

        /// <summary>
        /// Plays new music. If the music is paused, it resumes it.
        /// If the music is stopped, it prepares it again and plays it.
        /// Resets the changeMusic flag to make sure this isn't
        /// redundantly called again.
        /// </summary>
        private static void PlayNewMusic()
        {
            if (currentMusic == MusicList.Music1)
            {
                if (music1.IsPaused)
                    music1.Resume();
                else
                {
                    if (music1.IsStopped)
                        music1 = soundBank.GetCue(MUSIC1);

                    music1.Play();
                }
            }
            else if (currentMusic == MusicList.Music2)
            {
                if (music2.IsPaused)
                    music2.Resume();
                else
                {
                    if (music2.IsStopped)
                        music2 = soundBank.GetCue(MUSIC2);

                    music2.Play();
                }
            }
            else if (currentMusic == MusicList.Music3)
            {
                if (music3.IsPaused)
                    music3.Resume();
                else
                {
                    if (music3.IsStopped)
                        music3 = soundBank.GetCue(MUSIC3);
                    
                    music3.Play();
                }
            }
            else if (currentMusic == MusicList.MusicIntro)
            {
                if (musicIntro.IsPaused)
                    musicIntro.Resume();
                else
                {
                    if (musicIntro.IsStopped)
                        musicIntro = soundBank.GetCue(MUSIC_INTRO);

                    musicIntro.Play();
                }
            }

            changeMusic = false;
        }

        /// <summary>
        /// Prepares the music for playing.
        /// </summary>
        private static void ResetMusic()
        {
            music1 = soundBank.GetCue(MUSIC1);
            music2 = soundBank.GetCue(MUSIC2);
            music3 = soundBank.GetCue(MUSIC3);
            musicIntro = soundBank.GetCue(MUSIC_INTRO);
        }
        #endregion
    }
}
